/*
 * Created on Feb 20, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.text;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;

import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.vocabulary.RDF;

import edu.mit.simile.longwell.Cache;
import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.StructuredModelBase;
import edu.mit.simile.longwell.schema.ISchemaModel;

/**
 * @author dfhuynh
 */
public class LocalTextIndexModel extends StructuredModelBase implements
		ITextIndexModel {
	
	final static private Logger s_logger =
		Logger.getLogger(LocalTextIndexModel.class);

	protected IndexReader	m_indexReader;
	protected IndexSearcher	m_indexSearcher;
	protected boolean 		m_initialized;
	protected Cache			m_results = 
							new Cache(10, LocalTextIndexModel.class, "resuls");
	
	/**
	 * @param modelURI
	 * @param profile
	 * @param dir
	 */
	public LocalTextIndexModel(Profile profile, File dir) {
		super(profile, dir);
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredModel#dispose()
	 */
	public void dispose() {
		try {
			closeReaderSearcher();
		} catch (Exception e) {
			s_logger.error(e);
		}
		
		super.dispose();
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.ITextIndexModel#search(java.lang.String)
	 */
	public Set search(String text) {
		internalInitialize();

		Set objects = (Set) m_results.get(text);
		if (objects == null) {
			objects = new HashSet();
		
			try {
				org.apache.lucene.search.Query query = 
					QueryParser.parse("+" + text, "search", new SimpleAnalyzer());
	
				Hits hits = m_indexSearcher.search(query);
				
				ISchemaModel schemaModel = m_profile.getSchemaModel();
	
				for (int j = 0; j < hits.length(); j++) {
					String uri = hits.doc(j).getField("uri").stringValue();
					
					objects.add(schemaModel.pseudoURIToResource(uri));
				}
				
				m_results.put(text, objects);
			} catch (Exception e) {
				s_logger.error(e);
			}
		}		
		return objects;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onAfterAdd(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onAfterAdd(Model model) {
		super.onAfterAdd(model);
		
		internalInitialize();
		try {
			internalOnAfterAdd(model, false);
		} catch (Exception e) {
			s_logger.error(e);
		}
		m_results.clear();
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.IProfileListener#onAfterRemove(com.hp.hpl.jena.rdf.model.Model)
	 */
	public void onAfterRemove(Model model) {
		super.onAfterRemove(model);
		
		internalInitialize();
		internalOnAfterRemove(model);
		
		m_results.clear();
	}
	
	protected void internalInitialize() {
		if (m_initialized) {
			return;
		}

		try {
			if (!m_dir.exists()) {
				m_dir.mkdirs();
				
		        internalOnAfterAdd(m_profile.getModel(), true);
			}
			
			openReaderSearcher();
		} catch (Exception e){
			s_logger.error("Failed to initialize facade model", e);
		}
		
		m_initialized = true;
	}
	
	protected void internalOnAfterAdd(Model model, boolean create) throws Exception {
		try {
			closeReaderSearcher();
			
	        IndexWriter writer = new IndexWriter(
	        	m_dir.getAbsolutePath(), new SimpleAnalyzer(), create);
	        
	        ResIterator ri = model.listSubjectsWithProperty(RDF.type);
	        while (ri.hasNext()) {
                Resource object = ri.nextResource();
	                
                internalAddObject(writer, object, null);
	        }
	        
	        writer.optimize();
	        writer.close();
		} finally {
			openReaderSearcher();
		}
	}
	
	protected void internalOnAfterRemove(Model model) {
        ResIterator ri = model.listSubjectsWithProperty(RDF.type);
        while (ri.hasNext()) {
            Resource object = ri.nextResource();
                
            internalRemoveObject(object);
        }
	}
	
    protected void internalAddObject(
        	IndexWriter writer, Resource object, Document doc
    	) throws Exception {
    	
    	ISchemaModel schemaModel = m_profile.getSchemaModel();
    	Model model = m_profile.getModel();
    	
    	if (doc == null) {
        	String pseudoURI = schemaModel.resourceToPseudoURI(object);
        	
    		doc = new Document();
       		doc.add(Field.Keyword("uri", pseudoURI));
    	}
    	
    	StmtIterator si = model.listStatements(object, null, (RDFNode) null);
    	while (si.hasNext()) {
    		Statement	stmt = si.nextStatement();
			RDFNode 	n = stmt.getObject();
			String		v = n instanceof Literal ?
				((Literal) n).getString() :
				schemaModel.getLabel((Resource) n, null, true);
			
			doc.add(Field.Text("search", v));
    	}

    	writer.addDocument(doc);
    }
    
    protected void internalRemoveObject(Resource object) {
		try {
        	String pseudoURI = m_profile.getSchemaModel().resourceToPseudoURI(object);
	        	
    		Query 		query = QueryParser.parse("+" + pseudoURI, "uri", new SimpleAnalyzer());
    		Hits 		hits = m_indexSearcher.search(query);
    		
    		if (hits.length() > 0) {
    			m_indexReader.delete(hits.id(0));
    		}
		} catch (Exception e) {
			s_logger.error("Failed to remove object " + object, e);
		}

    }
	
    protected void openReaderSearcher() throws IOException {
		m_indexReader = IndexReader.open(m_dir.getAbsolutePath());
        m_indexSearcher = new IndexSearcher(m_indexReader);
    }
    
    protected void closeReaderSearcher() throws IOException {
    	if (m_indexReader != null) {
    		m_indexReader.close();
    		m_indexReader = null;
    	}
    	if (m_indexSearcher != null) {
    		m_indexSearcher.close();
    		m_indexSearcher = null;
    	}
    }
}
